package net.blank.ui.dragManager
{
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.display.Graphics;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.events.MouseEvent;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.ui.Mouse;
	import flash.ui.MouseCursor;
	import flash.utils.Dictionary;
	import flash.utils.getQualifiedClassName;
	
	/**
	 * 拖动管理器
	 * @author blank
	 * 
	 */
	public class DragManager
	{
		/**
		 * 静态实例
		 */
		private static var $instance:DragManager=new DragManager();
		
		/**
		 * 被拖动对象列表
		 */
		private var $dragObjs:Dictionary;
		
		/**
		 * 拖动对象的目标容器列表
		 */
		private var $dragContainers:Dictionary;
		
		/**
		 * 当前被拖动的对象
		 */
		private var $curDragObj:Sprite;
		
		/**
		 * 当前被拖动对象的 key 列表
		 */
		private var $curDragObjKeys:Vector.<String>;
		
		/**
		 * 当前被拖动的对象的舞台对象
		 */
		private var $curStage:Stage;
		
		/**
		 * 当前允许放置被拖动对象的容器
		 */
		private var $curContainer:DisplayObjectContainer;
		
		/**
		 * 当前被拖动对象的拖动图标
		 */
		private var $dragIcon:Shape;
		
		/**
		 * 偏移量
		 */
		private var $offset:Point;
		
		/**
		 * 拖动完成时的回调函数,函数必须接受三个参数:目标容器(DisplayObjectContainer),被拖动对象(Sprite),偏移量(Point)
		 */
		private var $dragCompleteFunc:Function;
		
		/**
		 * 拖动对象到目标容器上时触发,函数必须接受三个参数:目标容器(DisplayObjectContainer),被拖动对象(Sprite),偏移量(Point)
		 */
		private var $dragOverFunc:Function;
		
		/**
		 * 拖动对象离开目标容器时触发,函数必须接受三个参数:目标容器(DisplayObjectContainer),被拖动对象(Sprite),偏移量(Point)
		 */
		private var $dragOutFunc:Function;
		
		/**
		 * 验证目标容器
		 * @param container
		 * @return 
		 * 
		 */
		private function verifyContainer(container:DisplayObject):DisplayObjectContainer{
			if(!(container is DisplayObjectContainer)){
				container=container.parent;
			}
			if(container){
				do{
					var containerKeys:Dictionary=$dragContainers[container];
					if(containerKeys){
						for each(var key:String in $curDragObjKeys){
							if(key in containerKeys){
								return container as DisplayObjectContainer;
							}
						}
					}
					container=container.parent;
				}while(container)
			}
			return null;
		}
		
		/**
		 * 鼠标从当前容器上离开
		 * @param evt
		 * 
		 */
		private function rollOutForCurContainerHandler(evt:MouseEvent):void{
			$curContainer.removeEventListener(MouseEvent.ROLL_OUT,rollOutForCurContainerHandler);
			if($dragOutFunc != null){
				$dragOutFunc.apply(null,[$curContainer,$curDragObj,$offset]);
			}
			
			$curContainer=null;
		}
		
		/**
		 * 在拖动对象时,鼠标进入对象
		 * @param evt
		 * 
		 */
		private function mouseOverForStageHandler(evt:MouseEvent):void{
			$curContainer=verifyContainer(evt.target as DisplayObject);
			if($curContainer){
				if($dragOverFunc != null){
					$dragOverFunc.apply(null,[$curContainer,$curDragObj,$offset]);
				}
				$curContainer.addEventListener(MouseEvent.ROLL_OUT,rollOutForCurContainerHandler);
			}
		}
		
		/**
		 * 在拖动对象时,鼠标在舞台上移动
		 * @param evt
		 * 
		 */
		private function mouserMoveForStageHandler(evt:MouseEvent):void{
			$dragIcon.x=$curStage.mouseX;
			$dragIcon.y=$curStage.mouseY;
			
			evt.updateAfterEvent();
		}
		
		/**
		 * 启动对象的拖动后,在舞台上翻译鼠标
		 * @param evt
		 * 
		 */
		private function mouseUpForStageHandler(evt:MouseEvent):void{
			if(!$curContainer){
				$curContainer=verifyContainer(evt.target as DisplayObject);
			}
			
			if($curContainer){
				$curContainer.removeEventListener(MouseEvent.ROLL_OUT,rollOutForCurContainerHandler);
				if($dragOutFunc != null){
					$dragOutFunc.apply(null,[$curContainer,$curDragObj,$offset]);
				}
				
				if($dragCompleteFunc == null){
					var pos:Point=evt.target.globalToLocal(new Point($curStage.mouseX,$curStage.mouseY));
					
					$curDragObj.x=pos.x+$offset.x;
					$curDragObj.y=pos.y+$offset.y;
					
					$curContainer.addChild($curDragObj);
				}else{
					$dragCompleteFunc.apply(null,[$curContainer,$curDragObj,$offset]);
				}
				
				$curContainer=null;
			}else{
				if($dragCompleteFunc != null){
					$dragCompleteFunc.apply(null,[null,$curDragObj,null]);
				}
			}
			
			$curDragObj.visible=true;
			
			$dragIcon.visible=false;
			
			$curStage.removeEventListener(MouseEvent.MOUSE_MOVE,mouserMoveForStageHandler);
			$curStage.removeEventListener(MouseEvent.MOUSE_OVER,mouseOverForStageHandler);
			$curStage.removeEventListener(MouseEvent.MOUSE_UP,mouseUpForStageHandler);
			
			$curDragObj=null;
			$curDragObjKeys=null;
			$curStage=null;
			
			Mouse.cursor=MouseCursor.AUTO;
		}
		
		/**
		 * 开始拖动
		 * @param obj 被拖动对象
		 * @param dragCompleteFunc 拖动完成时的回调函数,函数必须接受三个参数:目标容器(DisplayObjectContainer),被拖动对象(Sprite),偏移量(Point)
		 * @param key 被拖动对象的 key
		 * @param dragIcon 拖动时的显示图标,如不指定,则默认显示被拖动对象
		 * @param offset 拖动时的偏移量,如不指定,将自动计算偏移量
		 * @param dragOverFunc 拖动对象到目标容器上时触发,函数必须接受三个参数:目标容器(DisplayObjectContainer),被拖动对象(Sprite),偏移量(Point)
		 * @param dragOutFunc 拖动对象离开目标容器时触发,函数必须接受三个参数:目标容器(DisplayObjectContainer),被拖动对象(Sprite),偏移量(Point)
		 * @return 拖动图标的绘图对象
		 */
		private function startDrag(obj:Sprite,dragCompleteFunc:Function=null,key:String=null,dragIcon:BitmapData=null,offset:Point=null,dragOverFunc:Function=null,dragOutFunc:Function=null):Graphics{
			$curDragObj=obj;
			$curStage=$curDragObj.stage;
			
			$dragCompleteFunc=dragCompleteFunc;
			$dragOverFunc=dragOverFunc;
			$dragOutFunc=dragOutFunc;
			
			/*拖动 key 验证*/
			if(key){
				addDragObj(obj,key);
			}else{
				if(!$dragObjs[obj]){
					return null;
				}
			}
			
			/*初化当前拖动对象的 key 列表*/
			$curDragObjKeys=new Vector.<String>();
			var objKeys:Dictionary=$dragObjs[$curDragObj];
			for each(var key:String in objKeys){
				$curDragObjKeys[$curDragObjKeys.length]=key;
			}
			$curDragObjKeys.fixed=true;
			
			/*验证拖动图标*/
			if(!dragIcon){
				dragIcon=new BitmapData($curDragObj.width,$curDragObj.height,true,0);
				dragIcon.draw($curDragObj);
			}
			
			/*验证偏移量*/
			if(!offset){
				var pos:Point=$curDragObj.parent.localToGlobal(new Point($curDragObj.x,$curDragObj.y));
				
				$offset.x=pos.x-$curStage.mouseX;
				$offset.y=pos.y-$curStage.mouseY;
			}else{
				$offset=offset;
			}
			
			/*绘制拖动图标*/
			$dragIcon.graphics.clear();
			var m:Matrix=new Matrix();
			m.translate($offset.x,$offset.y);
			$dragIcon.graphics.beginBitmapFill(dragIcon,m);
			$dragIcon.graphics.drawRect(0,0,dragIcon.width,dragIcon.height);
			
			$dragIcon.x=$curStage.mouseX;
			$dragIcon.y=$curStage.mouseY;
			
			/*添加拖动图标,隐藏被拖动对象*/
			$curStage.addChild($dragIcon);
			$dragIcon.visible=true;
			$curDragObj.visible=false;
			
			/*修改鼠标形状*/
			Mouse.cursor=MouseCursor.HAND;
			
			$curStage.addEventListener(MouseEvent.MOUSE_MOVE,mouserMoveForStageHandler);
			if($dragOverFunc != null){
				$curStage.addEventListener(MouseEvent.MOUSE_OVER,mouseOverForStageHandler);
			}
			$curStage.addEventListener(MouseEvent.MOUSE_UP,mouseUpForStageHandler);
			
			return $dragIcon.graphics;
		}
		
		/**
		 * 添加被拖动对象
		 * @param obj
		 * @param key
		 * 
		 */
		private function addDragObj(obj:Sprite,key:String):void{
			if(!(obj in $dragObjs)){
				$dragObjs[obj]=new Dictionary();
			}
			$dragObjs[obj][key]=key;
		}
		
		/**
		 * 移除被拖动对象
		 * @param obj
		 * @param key
		 * 
		 */
		private function rmDragObj(obj:Sprite,key:String=null):void{
			if(key){
				var keys:Dictionary=$dragObjs[obj];
				if(keys){
					if(key in keys){
						delete keys[key];
						for(key in keys){
							return;
						}
						delete $dragObjs[obj];
					}
				}
			}else{
				delete $dragObjs[obj];
			}
		}
		
		/**
		 * 添加拖动对象的目标容器
		 * @param container
		 * @param key
		 * 
		 */
		private function addContainer(container:DisplayObjectContainer,key:String):void{
			if(!(container in $dragContainers)){
				$dragContainers[container]=new Dictionary();
			}
			$dragContainers[container][key]=key;
		}
		
		/**
		 * 移除拖动对象的容器
		 * @param container
		 * @param key
		 * 
		 */
		private function rmContainer(container:DisplayObjectContainer,key:String=null):void{
			if(key){
				var keys:Dictionary=$dragContainers[container];
				if(keys){
					if(key in keys){
						delete keys[key];
						for(key in keys){
							return;
						}
						delete $dragContainers[container];
					}
				}
			}else{
				delete $dragContainers[container];
			}
		}
		
		/**
		 * 移除拖动 key
		 * @param key
		 * 
		 */
		private function rmKey(key:String):void{
			var keys:*;
			for(keys in $dragObjs){
				rmDragObj($dragObjs[keys],key);
			}
			for(keys in $dragContainers){
				rmContainer($dragContainers[keys],key);
			}
		}
		
		/**
		 * 清除所有被拖动对象和容器
		 * 
		 */
		private function clear():void{
			$dragObjs=new Dictionary();
			$dragContainers=new Dictionary();
		}
		
		/**
		 * 拖动管理器
		 * 
		 */
		public function DragManager(){
			if(!$instance){
				$dragObjs=new Dictionary();
				$dragContainers=new Dictionary();
				
				$dragIcon=new Shape();
				$dragIcon.alpha=.5;
				$offset=new Point();
			}else{
				throw new Error("只能用getInstance()来获取实例!");
			}
		}
		
		/**
		 * 开始拖动
		 * @param obj 被拖动对象
		 * @param dragCompleteFunc 拖动完成时的回调函数,函数必须接受三个参数:目标容器(DisplayObjectContainer),被拖动对象(Sprite),偏移量(Point)
		 * @param key 被拖动对象的 key
		 * @param dragIcon 拖动时的显示图标,如不指定,则默认显示被拖动对象
		 * @param offset 拖动时的偏移量,如不指定,将自动计算偏移量
		 * @param dragOverFunc 拖动对象到目标容器上时触发,函数必须接受三个参数:目标容器(DisplayObjectContainer),被拖动对象(Sprite),偏移量(Point)
		 * @param dragOutFunc 拖动对象离开目标容器时触发,函数必须接受三个参数:目标容器(DisplayObjectContainer),被拖动对象(Sprite),偏移量(Point)
		 * @return 拖动图标的绘图对象
		 */
		public static function startDrag(obj:Sprite,dragCompleteFunc:Function=null,key:String=null,dragIcon:BitmapData=null,offset:Point=null,dragOverFunc:Function=null,dragOutFunc:Function=null):Graphics{
			return $instance.startDrag(obj,dragCompleteFunc,key,dragIcon,offset,dragOverFunc,dragOutFunc);
		}
		
		/**
		 * 添加被拖动对象
		 * @param obj
		 * @param key
		 * 
		 */
		public static function addDragObj(obj:Sprite,key:String):void{
			$instance.addDragObj(obj,key);
		}
		
		/**
		 * 添加被拖动的目标容器
		 * @param key
		 * @param arg
		 * 
		 */
		public static function addDragObjsForAny(key:String,arg:*):void{
			var any:*;
			
			if(arg is Sprite){
				addDragObj(arg,key);
			}else if(arg is Vector.<Sprite>){
				for each(any in arg){
					addDragObj(any,key);
				}
			}else if(arg is Array || getQualifiedClassName(arg).indexOf("__AS3__.vec::Vector") != -1){
				for each(any in arg){
					addDragObjsForAny(key,any);
				}
			}else{
				throw new ArgumentError("arg 参数的类型只能是 Sprite ,或是 Sprite 的数组或 vector 数组");
			}
		}
		
		/**
		 * 添加被拖动的目标容器
		 * @param key
		 * @param args
		 * 
		 */
		public static function addDragObjs(key:String,...args):void{
			addDragObjsForAny(key,args);
		}
		
		/**
		 * 移除被拖动对象
		 * @param obj
		 * @param key
		 * 
		 */
		public static function rmDragObj(obj:Sprite,key:String=null):void{
			$instance.rmDragObj(obj,key);
		}
		
		/**
		 * 移除被拖动的对象
		 * @param key
		 * @param arg
		 * 
		 */
		public static function rmDragObjsForAny(key:String,arg:*):void{
			var any:*;
			
			if(arg is Sprite){
				rmDragObj(arg,key);
			}else if(arg is Vector.<Sprite>){
				for each(any in arg){
					rmDragObj(any,key);
				}
			}else if(arg is Array || getQualifiedClassName(arg).indexOf("__AS3__.vec::Vector") != -1){
				for each(any in arg){
					rmDragObjsForAny(key,any);
				}
			}else{
				throw new ArgumentError("arg 参数的类型只能是 Sprite ,或是 Sprite 的数组或 vector 数组");
			}
		}
		
		/**
		 * 移除被拖动的对象
		 * @param key
		 * @param arg
		 * 
		 */
		public static function rmDragObjs(key:String,...args):void{
			rmDragObjsForAny(key,args);
		}
		
		/**
		 * 添加拖动对象的目标容器
		 * @param container
		 * @param key
		 * 
		 */
		public static function addContainer(container:DisplayObjectContainer,key:String):void{
			$instance.addContainer(container,key);
		}
		
		/**
		 * 添加被拖动的目标容器
		 * @param key
		 * @param arg
		 * 
		 */
		public static function addContainersForAny(key:String,arg:*):void{
			var any:*;
			
			if(arg is DisplayObjectContainer){
				addContainer(arg,key);
			}else if(arg is Vector.<DisplayObjectContainer>){
				for each(any in arg){
					addContainer(any,key);
				}
			}else if(arg is Array || getQualifiedClassName(arg).indexOf("__AS3__.vec::Vector") != -1){
				for each(any in arg){
					addContainersForAny(key,any);
				}
			}else{
				throw new ArgumentError("arg 参数的类型只能是 DisplayObjectContainer ,或是 DisplayObjectContainer 的数组或 vector 数组");
			}
		}
		
		/**
		 * 添加被拖动的目标容器
		 * @param key
		 * @param args
		 * 
		 */
		public static function addContainers(key:String,...args):void{
			addContainersForAny(key,args);
		}
		
		/**
		 * 移除拖动对象的容器
		 * @param container
		 * @param key
		 * 
		 */
		public static function rmContainer(container:DisplayObjectContainer,key:String=null):void{
			$instance.rmContainer(container,key);
		}
		
		/**
		 * 移除拖动对象的容器
		 * @param key
		 * @param arg
		 * 
		 */
		public static function rmContainersForAny(key:String,arg:*):void{
			var any:*;
			
			if(arg is DisplayObjectContainer){
				rmContainer(arg,key);
			}else if(arg is Vector.<DisplayObjectContainer>){
				for each(any in arg){
					rmContainer(any,key);
				}
			}else if(arg is Array || getQualifiedClassName(arg).indexOf("__AS3__.vec::Vector") != -1){
				for each(any in arg){
					rmContainersForAny(key,any);
				}
			}else{
				throw new ArgumentError("arg 参数的类型只能是 DisplayObjectContainer ,或是 DisplayObjectContainer 的数组或 vector 数组");
			}
		}
		
		/**
		 * 移除拖动对象的容器
		 * @param key
		 * @param args
		 * 
		 */
		public static function rmContainers(key:String,...args):void{
			rmContainersForAny(key,args);
		}
		
		/**
		 * 移除拖动 key
		 * @param key
		 * 
		 */
		public static function rmKey(key:String):void{
			$instance.rmKey(key);
		}
		
		/**
		 * 清除所有被拖动对象和目标容器
		 * <p><font color="#ff0000">警告:使用此方法会清除所有的被拖动对象的目标容器,使用前请先确定这些对象和容器将不会在使用或接受拖动.否则请使用 rmKey 或是其它移除被拖动对象和容器的方法</font></p>
		 */
		public static function clear():void{
			$instance.clear();
		}
	}
}